package cn.lg.soar.common.util.reflect;

import cn.lg.soar.common.util.asserts.InnerAssert;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * 方法反射工具
 * @author luguoxiang
 * @date 2022/2/17
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
class MethodUtils {

    /**
     * 调用构造函数
     * @param clazz
     * @param params
     * @param <T>
     * @return
     */
    static <T> T constructor(Class<T> clazz, Object ...params) {
        try {
            Constructor<?>[] constructors = clazz.getConstructors();
            if (constructors.length == 1) {
                return (T) constructors[0].newInstance(params);
            }
            int paramLen = params.length;
            Class<?>[] classes = Arrays.stream(params)
                    .map(x -> x == null ? null : x.getClass())
                    .toArray(Class[]::new);
            for (int i = 0, len = constructors.length; i < len; i++) {
                Constructor<?> constructor = constructors[i];
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                if (parameterTypes.length != paramLen) {
                    continue;
                }
                boolean flag = true;
                for (int j = 0; j < paramLen; j++) {
                    Class<?> pclass = classes[j];
                    if (pclass == null || parameterTypes[j].isAssignableFrom(pclass)) {
                        continue;
                    }
                    flag = false;
                    break;
                }
                if (flag) {
                    return (T) constructor.newInstance(params);
                }
            }
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        throw new RuntimeException(clazz.getName() + " constructor target not found");
    }

    static <T>boolean hasConstructor(Class<T> clazz, Object ...params) {
        Constructor<?>[] constructors = clazz.getConstructors();
        int paramLen = params.length;
        Class<?>[] classes = Arrays.stream(params)
                .map(x -> x == null ? null : x.getClass())
                .toArray(Class[]::new);
        for (Constructor<?> constructor : constructors) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            // 验证参数长度
            if (parameterTypes.length != paramLen) {
                continue;
            }
            // 验证参数类型
            boolean flag = true;
            for (int j = 0; j < paramLen; j++) {
                Class<?> pclass = classes[j];
                if (pclass == null || parameterTypes[j].isAssignableFrom(pclass)) {
                    continue;
                }
                // 类型无法匹配
                flag = false;
                break;
            }
            // 是否找到了
            if (flag) {
                return true;
            }
        }
        return false;
    }

    /**
     * 调用函数
     * @param o
     * @param methodName
     * @param params
     * @return
     */
    static Object invoke(Object o, String methodName, Object...params) {
        InnerAssert.notEmpty(methodName, "methodName cannot be blank");
        Class<?> aClass = o.getClass();
        Method[] methods = aClass.getMethods();
        Method[] targetMethods = new Method[methods.length];
        int count = 0;
        for (Method method : methods) {
            if (!method.getName().equals(methodName)) {
                continue;
            }
            targetMethods[ count++ ] = method;
        }
        if (count == 1) {
            return invoke(o, targetMethods[0], params);
        }
        if (count == 0) {
            throw new RuntimeException(aClass.getName() + " not found method " + methodName);
        }

        int paramLen = params.length;
        Class<?>[] classes = Arrays.stream(params).map(x -> x == null ? null : x.getClass()).toArray(Class[]::new);
        for (int i = 0; i < count; i++) {
            Method method = targetMethods[i];
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != paramLen) {
                continue;
            }
            boolean flag = true;
            for (int j = 0; j < paramLen; j++) {
                Class<?> pclass = classes[j];
                if (pclass == null || parameterTypes[j].equals(pclass)) {
                    continue;
                }
                flag = false;
                break;
            }
            if (flag) {
                return invoke(o, method, params);
            }
        }
        throw new RuntimeException(aClass.getName() + "." + methodName + " invocation target not found");
    }

    static Object invoke(Object o, Method method, Object...params) {
        try {
            method.setAccessible(true);
            if (ModifierUtils.isStatic(method)) {
                return method.invoke(null, params);
            }
            return method.invoke(o, params);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 扫描 public 方法
     * @param clazz
     * @param consumer
     * @param predicate
     */
    static void scan(Class<?> clazz, Consumer<Method> consumer, Predicate<Class<?>> predicate) {
        if (clazz == null) {
            return;
        }
        if (predicate.test(clazz)) {
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (Modifier.isPublic(method.getModifiers())) {
                    consumer.accept(method);
                }
            }
            scan(clazz.getSuperclass(), consumer, predicate);
        }
    }

    /**
     * 扫描所有方法
     * @param clazz
     * @param consumer
     * @param predicate
     */
    static void scanAllMethod(Class<?> clazz, Consumer<Method> consumer, Predicate<Class<?>> predicate) {
        if (clazz == null) {
            return;
        }
        if (predicate.test(clazz)) {
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                consumer.accept(method);
            }
            scanAllMethod(clazz.getSuperclass(), consumer, predicate);
        }
    }

}
